// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using ValidationAspects;
using ValidationAspects.Exceptions;
using ValidationAspects.Sdk;

namespace TestValidationAspects
{
	[TestFixture]
	public class TestExceptionHierarchy
	{
		[SetUp]
		public void Setup()
		{
			TestHelpers.ResetFramework();
		}

		[Test]
		public void Test_Exception_hierachy_for_object_validation()
		{
			// expect
			// C(0)			A
			//  C(1)		A
			//   IB			A
			//   C(2)		B
			//    O			B
			//    C(3)		B
			//     B		B
			//     C(4)		B
			//      BV1		B
			//      BV2		B
			//    IB		B

			// C = ValidationContextException

			typeof(IB).AddValidation(new[] {new ValidateIB()});
			typeof(B).AddValidation(new[] {new ValidateB()});
			PropertyInfo propertyB = TypeOf<A>.GetProperty(x => x.B);
			propertyB.AddValidation(new[] {new ValidateIB()});

			A a = new A();
			B b = new B();

			a.B = b;

			var r = a.Validate();
			Assert.IsFalse(r.IsValid);

			ValidationContextException contextException0 = (ValidationContextException)r.ValidationException;

			// test nested hierarchy
			contextException0.Context.TestValues(a, typeof(A), null, null, null, null);
			Assert.AreEqual(1, contextException0.Exceptions.Count());

			ValidationContextException contextException1 = contextException0.Exceptions.OfType<ValidationContextException>().First();
			MethodInfo method = propertyB.GetSetMethod();
			contextException1.Context.TestValues(a, typeof(A), propertyB, method, method.GetParameters()[0], new object[] {b});
			Assert.AreEqual(2, contextException1.Exceptions.Count());

			ValidateIBException ibException1 = contextException1.Exceptions.OfType<ValidateIBException>().First();
			ibException1.Context.TestValues(a, typeof(A), propertyB, method, method.GetParameters()[0], new object[] {b});

			ValidationContextException contextException2 = contextException1.Exceptions.OfType<ValidationContextException>().First();
			contextException2.Context.TestValues(b, typeof(IB), null, null, null, null);
			Assert.AreEqual(3, contextException2.Exceptions.Count());

			ValidateObjectException objectException = contextException2.Exceptions.OfType<ValidateObjectException>().First();
			objectException.Context.TestValues(b, typeof(IB), null, null, null, null);

			ValidationContextException contextException3 = contextException2.Exceptions.OfType<ValidationContextException>().First();
			contextException3.Context.TestValues(b, typeof(B), null, null, null, null);
			Assert.AreEqual(2, contextException3.Exceptions.Count());

			ValidateBException bException = contextException3.Exceptions.OfType<ValidateBException>().First();
			bException.Context.TestValues(b, typeof(B), null, null, null, null);

			ValidationContextException contextException4 = contextException3.Exceptions.OfType<ValidationContextException>().First();
			contextException4.Context.TestValues(b, typeof(B), null, null, null, null);
			Assert.AreEqual(2, contextException4.Exceptions.Count());

			ValidateBV1Exception bv1Exception = contextException4.Exceptions.OfType<ValidateBV1Exception>().First();
			bv1Exception.Context.TestValues(b, typeof(B), null, typeof(B).GetMethod("Validate1"), null, new object[] {b});

			ValidateBV2Exception bv2Exception = contextException4.Exceptions.OfType<ValidateBV2Exception>().First();
			bv2Exception.Context.TestValues(b, typeof(B), null, typeof(B).GetMethod("Validate2"), null, new object[] {b});

			ValidateIBException ibException2 = contextException2.Exceptions.OfType<ValidateIBException>().First();
			ibException2.Context.TestValues(b, typeof(IB), null, null, null, null);

			// test flattened
			ValidationException[] exceptions = contextException1.Flatten().ToArray();
			Assert.AreEqual(6, exceptions.Count());
			Assert.AreSame(ibException1, exceptions[0]);
			Assert.AreSame(objectException, exceptions[1]);
			Assert.AreSame(bException, exceptions[2]);
			Assert.AreSame(bv1Exception, exceptions[3]);
			Assert.AreSame(bv2Exception, exceptions[4]);
			Assert.AreSame(ibException2, exceptions[5]);

			Assert.AreEqual(6, contextException1.Messages.Count());
		}

		public class A
		{
			[ValidateObject]
			public IB B { get; set; }

			public void Run([ValidateObject] IB b) {}
		}

		public interface IB {}

		public class ValidateBV1Exception : ValidationException {}

		public class ValidateBV2Exception : ValidationException {}

		public class ValidateBException : ValidationException {}

		public class ValidateIBException : ValidationException {}

		public class B : IB
		{
			[ValidationMethod]
			public static void Validate1(B b)
			{
				throw new ValidateBV1Exception();
			}

			[ValidationMethod]
			public static void Validate2(B b)
			{
				throw new ValidateBV2Exception();
			}
		}

		public class ValidateIB : ValidatorFactoryBase<IB>
		{
			#region Overrides of ValidatorFactoryBase<IB>
			protected override Action<IB, IValidationContext> ValidateAction
			{
				get { return (v, c) => { throw new ValidateIBException(); }; }
			}
			#endregion
		}

		public class ValidateB : ValidatorFactoryBase<B>
		{
			#region Overrides of ValidatorFactoryBase<IB>
			protected override Action<B, IValidationContext> ValidateAction
			{
				get { return (v, c) => { throw new ValidateBException(); }; }
			}
			#endregion
		}
	}
}